package com.me.batm.models;

import java.util.ArrayList;
import java.util.Iterator;

import aurelienribon.tweenengine.Timeline;
import aurelienribon.tweenengine.Tween;
import aurelienribon.tweenengine.TweenManager;
import aurelienribon.tweenengine.equations.Bounce;
import aurelienribon.tweenengine.equations.Linear;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.g2d.tiled.TileAtlas;
import com.badlogic.gdx.graphics.g2d.tiled.TileSet;
import com.badlogic.gdx.graphics.g2d.tiled.TiledMap;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import com.me.batm.GameConstants;
import com.me.batm.Utils;
import com.me.batm.ai.TargetAIService;
import com.me.batm.ai.Waypoint;
import com.me.batm.input.InputStatus;
import com.me.batm.tweens.TargetAccessor;

public class GameWorld {
	private static final String TAG = GameWorld.class.getSimpleName();
	
	private static final float SPEED_FACTOR = 0.5f;
	private static final float CREATION_TIME = 5;
	private static final float AURA_RADIO = 150f;
	private static final float TIME_DURATION = 2;
	private static final int PROJECTILE_NUM = 10;
	private static final float PROJECTILE_VELOCITY = 120;
	private static final int NUMBER_OF_AURAS = 10;
	private static final float LIFE_DIVISOR = 2;
	private static final float DAMAGE = 5f;
	
	public boolean playTeleportSound;
	
	
	public InputStatus inputStatus;
	private TweenManager tweenManager = new TweenManager();

	// map
	public TiledMap tiledMap;
	public TileAtlas tileAtlas;
	public boolean walkMap[][];
	
	//Game Objects
	public Player player;
	public Target target;
	public Array<Rectangle> mapRectangles;
	public int [][] tiles;
//	System.out.println(tiledMap.layers.get(0).tiles[0][0]);
	public int layerWidth;
	public int layerHeight;
	public int tileWidth;
	public int tileHeight;
	public float timeToCreate;
	
	private int prevTargetX,prevTargetY;
	
	// Game obstacles
//	public Collection<GameEntity> obstacles;
	public Array<GameEntity> entities;
	
	public boolean normalSpeed;
	public boolean ignoreFirstDelta;

	public GameWorld(InputStatus inputStatus, TiledMap tiledMap, TileAtlas tiledAtlas, float timeOfLevel){
		this.inputStatus = inputStatus;
		this.tiledMap = tiledMap;
		this.tileAtlas = tiledAtlas;
		this.entities = new Array<GameEntity>();
		this.mapRectangles = new Array<Rectangle>();
		this.timeToCreate = CREATION_TIME; 
		
		player = new Player(100f, 100f, new Vector2(0,-1f));
		target = new Target(200f,200f,new Vector2(0,1f));
		player.setTimeOfLife(timeOfLevel);

		setTilesVariables();
		createMapRectangles();
		createWalkMap();
		
		createPlayer(timeOfLevel);
		createTarget();
		generateAuras(NUMBER_OF_AURAS);
		
		playTeleportSound = false;
		
//<<<<<<< .mine
		normalSpeed = true;
//				
//		// inverse aura
//		this.entities.add(new InverseAura(80f, 400f, 80, 80, new Vector2(0, 0), 150));
//		//this.auras.add(new SpeedAura(400f, 80f, 80, 80, new Vector2(0, 0), 150, 0.5f));
//		this.entities.add(new TeleportAura(400f, 80f, 80, 80, new Vector2(0, 0), 150));
//		this.entities.add(new TimedSpeed(85f, 150f, 40f, 40f, new Vector2(0, 0),5f, 0.5f));
//=======
//		
//>>>>>>> .r99
//		
//<<<<<<< .mine
	}

	private void createTarget() {
		int x, y;
		float targetX, targetY;
		while(true) {
			x = (int) Math.floor(Math.random() * layerWidth);
			y = (int) Math.floor(Math.random() * layerHeight);
	//		System.out.println("Coords: " + x + " " + y);
			
			if (walkMap[x][y] == true)  {
				targetX = y * tileWidth + 2;
				targetY = (layerHeight - x - 1) * tileHeight + 2;
			
			// TODO Auto-generated method stub
				target = new Target(targetX, targetY,new Vector2(0,1f));
				break;
			}
		}
	}

	private void createPlayer(float timeOfLevel) {
		// TODO Auto-generated method stub
		int x, y;
		float playerX, playerY;
		while(true) {
			x = (int) Math.floor(Math.random() * layerWidth);
			y = (int) Math.floor(Math.random() * layerHeight);
			
			if (walkMap[x][y] == true)  {
				playerX = y * tileWidth + 2;
				playerY = (layerHeight - x - 1) * tileHeight + 2;
			
				player = new Player(playerX, playerY, new Vector2(0,-1f));
				player.setTimeOfLife(timeOfLevel);		
				break;
			}
		}
		ignoreFirstDelta = true;
	}
	
	public GameEntity entityFactory (String type, int direction, int x, int y, float width, float height, float velocity) {
		GameEntity entity;
		float speedFactor = SPEED_FACTOR;
		float duration = TIME_DURATION;
		float radio = AURA_RADIO;
		float lifeDivisor = LIFE_DIVISOR;
		boolean [] positive = {true, false, false};
		int index;
		index = (int) Math.floor(Math.random() * 3);
		
		if (type.equals("TimedSpeed")) {
			entity = new TimedSpeed(direction, x, y, width, height, new Vector2(0, 0), velocity, duration, speedFactor);
		} else if (type.equals("TimedReverse")) {
			entity = new TimedControlReverse(direction, x, y, width, height, new Vector2(0, 0), velocity , duration);
		} else if (type.equals("SpeedAura")) {
			entity = new SpeedAura(direction, x, y, width, height, new Vector2(), velocity, radio, speedFactor, positive[index]);
		} else if (type.equals("TeleportAura")) {
			entity = new TeleportAura(direction, x, y, width, height, new Vector2(), velocity, radio);
		} else if (type.equals("ReverseAura")) {
			entity = new InverseAura(direction, x, y, width, height, new Vector2(), velocity, radio);
		} else if (type.equals("LifeAura")) {
			entity = new LifeAura(direction, x, y, width, height, new Vector2(), radio, velocity, lifeDivisor, false);
		} else if (type.equals("LifeBullet")) {
			entity = new LifeBullet(direction, x, y, width, height, new Vector2(), velocity, duration, DAMAGE);
		} else if (type.equals("TimedBlur")) {
			entity = new TimedBlur(direction, x, y, width, height, new Vector2(), velocity, duration);
		} else {
			entity = null;
		}
		return entity;
	}
	public void generateAuras(int number) {
		int i;
		int x, y, z, type;
		int realX, realY;
		String [] auras = {"SpeedAura", "TeleportAura", "ReverseAura", "LifeAura"};
		for (i = 0; i < number; i += 1) {
			do {
				x = (int) Math.floor(Math.random() * layerHeight);
				y = (int) Math.floor(Math.random() * layerWidth);
				
			} while (walkMap[x][y] == false);
			realX = y * tileWidth + 2;
			realY = (layerHeight - x - 1) * tileHeight + 2;
			z = (int) Math.floor(Math.random() * 4);
			type = (int) Math.floor(Math.random() * 4);
			this.entities.add(entityFactory(auras[type], z, realX, realY, 80, 80, 0));	
		}
	}
	
	public void generateProjectil() {
		int x, y, z, index;
		int realX, realY;
		float velocity = PROJECTILE_VELOCITY;
		String [] types = {"TimedSpeed", "TimedReverse", "LifeBullet", "TimedBlur"};
		x = (int) Math.floor(Math.random() * layerWidth);
		y = (int) Math.floor(Math.random() * layerHeight);
		z = (int) Math.floor(Math.random() * 4);
		index = (int) Math.floor(Math.random() * 4);
		if (walkMap[x][y] == true)  {
			realX = y * tileWidth + 2;
			realY = (layerHeight - x - 1) * tileHeight + 2;
			this.entities.add(entityFactory(types[index], z, realX, realY, 20, 20, velocity));
		} else {
			generateProjectil();
		}
	}
	public void createWalkMap(){
		int [][] tiles = tiledMap.layers.get(0).tiles;
		TileSet tile = null;
		
		walkMap = new boolean[tiles.length][tiles[0].length];
		
		for(int i = 0; i < tiles.length; i += 1) {
			for (int j = 0; j < tiles[0].length; j += 1) {
				for (int k = 0; k < tiledMap.tileSets.size(); k += 1) {
					tile = tiledMap.tileSets.get(k);
					if (tile.firstgid == tiles[i][j]) {
						break;
					}
				}
				walkMap[i][j] = tile.name.contains("grass");
			}
		}
	}
	
	public void setTilesVariables() {
		tiles = tiledMap.layers.get(0).tiles;
//		System.out.println(tiledMap.layers.get(0).tiles[0][0]);
		TileSet tile = tiledMap.tileSets.get(tiledMap.layers.get(0).tiles[0][0] - 1);
		
		layerWidth = tiledMap.layers.get(0).getWidth();
		layerHeight = tiledMap.layers.get(0).getHeight();
		tileWidth = tile.tileWidth;
		tileHeight = tile.tileHeight;
//		System.out.println(layerWidth + " " + layerHeight + " " + tileWidth + " " + tileHeight);
	}
	
	public void createMapRectangles () {
		int i, j, k;
//		System.out.println(tiledMap.layers.get(0).tiles[0][0]);
		TileSet tile = tiledMap.tileSets.get(tiledMap.layers.get(0).tiles[0][0] - 1);
		int id;
		int x, y;
//		System.out.println(layerWidth + " " + layerHeight);
		for (i = 0; i < tiles.length; i += 1) {
			for (j = 0; j < tiles[0].length; j += 1) {
//				System.out.println(i + " " + j);
				id = tiles[i][j];
//				System.out.println(id);
				for (k = 0; k < tiledMap.tileSets.size(); k++) {
					tile = tiledMap.tileSets.get(k);					
					if (tile.firstgid == id) {
						break;
					}
				}
				if (!tile.name.contains("grass")){
					x = j * tileWidth;
					y = (layerHeight - i  - 1) * tileHeight;
					mapRectangles.add(new Rectangle(x, y, tileWidth, tileHeight));
				}
			}
		}
		
		Utils.tileWidth = tileWidth;
		Utils.tileHeight = tileHeight;
		
		Utils.layerWidth = layerWidth;
		Utils.layerHeight = layerHeight;
	}
	
	private void managePlayer(float delta){		
		
		boolean wontCrash;
		
		GameEntity entity;
		Vector2 direction = new Vector2(0, 0);

		int i, j;
	
		
		if(player.isAlive == false) {
			return;		
		}
		
		if(inputStatus.isLeft)
			direction.add(player.directionMultiplier[Player.LEFT].tmp().mul(delta));
		
		if(inputStatus.isRight)
			direction.add(player.directionMultiplier[Player.RIGHT].tmp().mul(delta));

		if(inputStatus.isDown)
			direction.add(player.directionMultiplier[Player.DOWN].tmp().mul(delta));

		if(inputStatus.isUp)
			direction.add(player.directionMultiplier[Player.UP].tmp().mul(delta));

		// TODO Create an instance of an auxiliary Rectangle in the constructor
		wontCrash = !Utils.willCrash(
				new Rectangle(player.getX() + direction.x * GameConstants.PLAYER_SPEED_CONSTANT,
					player.getY() + direction.y * GameConstants.PLAYER_SPEED_CONSTANT,
					player.shape.width,
					player.shape.height),
				mapRectangles);
		
		normalSpeed = !inputStatus.isSpace;
		
		player.move(delta, direction, wontCrash && !inputStatus.isSpace && player.teletransportTime > 1f && target.teletransportTime > 1f);
		
		// apply auras
		for (i = 0; i < entities.size; i += 1) {
			entity = entities.get(i);
			// update the collided property
			entity.isColliding(player);
			
		}
		
		player.updateTimedAlterations(delta);
		if (player.teleport) {
			player.teletransport(walkMap, layerWidth, layerHeight, tileWidth, tileHeight, entities);
			player.teletransportTime = 0f;
			playTeleportSound = true;
		}

	}

	private void manageTarget(float delta){
		//target.move(delta, new Vector2(0f,0f));
		
		if(target.isConquered() == false && target.mustTeletransport()){
			target.teleport = true;
			tweenManager.killAll();
			target.teletransport(walkMap, layerWidth, layerHeight, tileWidth, tileHeight, entities);
			target.angerBar = 0;
			target.isWalking = false;
			target.teleport = false;
			target.teletransportTime = 0f;
			
			playTeleportSound = true;
		}
		
		if(target.isWalking == false){
			moveTarget();
		}else {
			target.update(delta);
		}		
	}
	
	private void manageEntities(float delta) {
		int i;
		float x, y;
		GameEntity entity;
		for (Iterator<GameEntity> iter = entities.iterator(); iter.hasNext();) {
			entity = iter.next();
			entity.move(delta);
			x = entity.getX();
			y = entity.getY();
			if (x < 0 || y < 0 || x > layerWidth * tileWidth || y > layerHeight * tileHeight) {
				iter.remove();
			}
		}
		timeToCreate -= delta;
		if (timeToCreate < 0) {
			for (i = 0; i < PROJECTILE_NUM; i += 1) {
				generateProjectil();
			}
			timeToCreate = CREATION_TIME;
		}
	}
	private void moveTarget(){
		Vector2 playerPos = Utils.getXYToTilePosition(player.shape.x, player.shape.y);
		Vector2 targetPos = Utils.getXYToTilePosition(target.shape.x, target.shape.y);
		
		Vector2 nextMove = TargetAIService.selectNextWaypoint(walkMap, (int)targetPos.x,(int)targetPos.y,prevTargetX,prevTargetY,(int)playerPos.x,(int)playerPos.y);
		if(nextMove == null)return;

		int nextX = (int) nextMove.x;
		int nextY = (int) nextMove.y;

		//Gdx.app.log(TAG,String.format("Moving from %d,%d to %d,%d",(int)targetPos.x,(int)targetPos.y,nextX,nextY));
		
//		Gdx.app.log(TAG,String.format("Moving from %d,%d to %d,%d",(int)targetPos.x,(int)targetPos.y,nextX,nextY));
		
		Vector2 newPosition = Utils.getTilePositionToXY(nextX, nextY);
		float nextXW = newPosition.x - Target.WIDTH / 2f;
		float nextYW = newPosition.y - Target.HEIGHT / 2f;
		
		Timeline.createSequence()
			.push(Tween.to(target, TargetAccessor.TARGET_MOVE, GameConstants.TARGET_TILE_CHANGE_TIME)
					.target(nextXW,nextYW)
					.ease(Linear.INOUT))
			.push(Tween.to(target, TargetAccessor.TARGET_FINISH_MOVE, 0.1f)
					.target(0))
			.start(tweenManager);
	}

	private void manageTargetPlayerDistance(float delta){
		if(target.isConquered())
			return;
		
		float dist = (float) Math.hypot(player.shape.x - target.shape.x, player.shape.y - target.shape.y);
		float sendDist = GameConstants.MINIMUM_CONQUER_RADIO_AFFECT - dist;

		if(dist <= GameConstants.MINIMUM_CONQUER_RADIO_AFFECT)
			target.applyConquerDistance(sendDist,delta);
		if(dist <= GameConstants.MINIMUM_ANGER_RADIO_AFFECT)
			target.applyAngerDistance(sendDist,delta);

		if(dist > GameConstants.MINIMUM_CONQUER_RADIO_AFFECT){
			target.angerBar = Math.max(0f,target.angerBar - delta * GameConstants.RECOVER_ANGER);
		}
	}

	public void update(float delta) {
		if(ignoreFirstDelta){
			ignoreFirstDelta = false;
			return;
		}

		if(!inputStatus.isPause) {
			tweenManager.update(delta);
			
			target.teletransportTime += delta;
			player.teletransportTime += delta;
			
			if(inputStatus.isTele){
				target.angerBar = 110f;
				target.conquerBar = 0f;
			}

			manageTargetPlayerDistance(delta);
			
			if(inputStatus.isWin){
				target.conquerBar = 100f;
				target.angerBar = 0f;
			}
			if(inputStatus.isDeath){
				target.conquerBar = 0f;
				player.life = 0f;
			}
			
			manageTarget(delta);
			manageEntities(delta);
			managePlayer(delta);
		}
	}

	public void dispose() {
		tileAtlas.dispose();
		mapRectangles.clear();
		entities.clear();
	}
}
